﻿using System;
using System.Linq;
using System.Collections.Generic;
using RLProject1.Model;
using System.Threading.Tasks;
using Bettzueche.RLLibrary.Component;
using Bettzueche.RLLibrary.Functions;

namespace RLProject1 {

    /// <summary>
    /// Implementierung des Sarsa-Lambda Algorithmus für eine GridWorld.
    /// 
    /// <para>
    ///  1. Initialize Q(s,a) arbitrarily and e(s,a) = 0, for all s,a</para><para>
    ///  2. Repeat (for each episode):</para><para>
    ///  3.  Initialize s,a</para><para>
    ///  4.    Repeat (for each step of episode):</para><para>
    ///  5.    Take action a, observe r, s'</para><para>
    ///  6.    Choose a' from s' using policy derived from Q (e.g., epsilon-greedy)</para><para>
    ///  7.    delta = r + gamma*Q(s',a') - Q(s,a)</para><para>
    ///  8.    e(s,a) += 1</para><para>
    ///  9.    For all s,a:</para><para>
    /// 10.      Q(s,a) += alpha * delta * e(s,a)</para><para>
    /// 11.      e(s,a) *= gamma * lambda</para><para>
    /// 12.    s = s', a = a'</para><para>
    /// 13.  until s is terminal</para><para>
    /// </para>
    /// </summary>
    /// <remarks>Die 'Magic' steckt in SarsaLambda{S,A}</remarks>
    public class SarsaForDummies : SarsaLambda<GridState,GridAction>
    {
        #region für-die-View Krams

        /// <summary>
        /// Wahrscheinlichkeit, mit der nicht die beste Aktion gewählt wird, sondern eine beliebige andere.
        /// </summary>
        public double ExplorationFactor {
            get { return (Policy as EpsilonGreedyForGrid).Epsilon; }
            set { Policy = new EpsilonGreedyForGrid(value); }
        }

        #endregion

        /// <summary>
        /// Gets or sets the environment.
        /// </summary>
        /// <value>
        /// The environment.
        /// </value>
        public new GridWorld Environment {
            get { return base.Environment as GridWorld; }
            set { base.Environment = value; }
        }

        /// <summary>
        /// alle Q-Werte
        /// </summary>
        /// <value>
        /// The Q function.
        /// </value>
        public new GridWorldQValues QFunction {
            get { return base.QFunction as GridWorldQValues; }
            set { base.QFunction = value; }
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="env">Umwelt, auf der Aktionen ausgeführt werden</param>
        /// <param name="policy">Startegie</param>
        /// <param name="learnRate">Faktor aus [0,1], der angibt, in welchem Maße die aktuelle Erfahrungen in die Startegiebewertung einfließt</param>
        /// <param name="discount">Faktor aus [0,1], für eine absteigende Gewichtung aller Rewards</param>
        /// <param name="traceDecay">Faktor aus [0,1] für eine absteigende Gewichtung der Verantwortlichkeit früherer Aktionen auf die aktuelle Belohnung.</param>
        public SarsaForDummies(GridWorld env, GenericExplorationPolicy<GridAction> policy, double learnRate, double discount, double traceDecay)
            : this(env, policy, new GridWorldQValues(env.StateCount), learnRate, discount, traceDecay) { }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="env"></param>
        /// <param name="policy"></param>
        /// <param name="qVals"></param>
        /// <param name="learnRate"></param>
        /// <param name="discount"></param>
        /// <param name="traceDecay"></param>
        public SarsaForDummies(GridWorld env, GenericExplorationPolicy<GridAction> policy, GridWorldQValues qVals, double learnRate, double discount, double traceDecay) :
            base(env, qVals, policy)
        {
            if (policy == null)
                throw new ArgumentNullException("policy is null");
            if (env == null)
                throw new ArgumentNullException("env is null");
            if (qVals == null)
                throw new ArgumentNullException("qVals is null");
            //this.QFunction = qVals;
            //this.Policy = policy;
            //this.Environment = env;
            this.LearnRate = learnRate;
            this.DiscountFactor = discount;
            this.TraceDecay = traceDecay;
            this.Init(null, null);
            base.EFunction.MinE = 0.01;
        }

        /// <summary>
        /// Default Lernalgorithmus. Nur die aktuelle Belohnung wird für die Berwertung nur der letzten Zustand/Aktion herangezogen.
        /// </summary>
        /// <param name="env">Umwelt</param>
        /// <param name="policy">Strategie</param>
        public SarsaForDummies(GridWorld env, EpsilonGreedyForGrid policy)
            : this(env, policy, 0, 0, 0) {
        }


        /// <summary>
        /// Initialisierung der Q- und e-Werte mit 0.
        /// </summary>
        public override void Init(GridState initialState, string specifications) {
            base.Init(initialState, specifications);
            // 1. Initialize Q(s,a) arbitrarily and e(s,a) = 0, for all s,a
            // qValues wurden übergeben oder im Konstruktor erzeugt
            // für die Efunktion wird Default der Basisklasse verwendet.
        }

        /// <summary>
        /// Kennzeichnet den Beginn einer neuen Lernepisode. Zuvor ausgeführet Aktionen und besuchte Zustände
        /// werden nicht weiter berücksichtigt.
        /// </summary>
        public override void Reset() {
            base.Reset();
        }

        /// <summary>
        /// Setzt alle Erfahrungen zurück.
        /// </summary>
        public void resetAll() {
            base.Reset();
            this.Init(null, null);
            this.QFunction = new GridWorldQValues(Environment.StateCount);
        }
    }
}
